﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Affinity.Core.Collections;

namespace Affinity.Flux
{
    /// <summary>
    /// The container/context defining the scope of interaction of a set of component objects.
    /// </summary>
    public class Context
    {
        /// <summary>
        /// Constructor accepting an optional implementation of IMatchMaker - if omitted or null, StandardMatchMaker is used.
        /// </summary>
        /// <param name="matchMaker"></param>
        public Context(IMatchMaker matchMaker = null)
        {
            _matchMaker = matchMaker ?? new StandardMatchMaker();
        }

        /// <summary>
        /// Registers a component with this context.
        /// </summary>
        /// <param name="component"></param>
        public void RegisterComponent(object component)
        {
            if (component == null) throw new ArgumentNullException("component");

            lock (_sync)
            {
//                _LogChannels.Trace("Registering component " + component);
//#if DEBUG
//                if (_RegisteredComponents.Contains(component)) throw new Exception("Component already registered");
//#endif
                Component componentWrapper;

                if (!_registeredComponents.TryGetValue(component, out componentWrapper))
                {
                    componentWrapper = new Component(component);

                    _registeredComponents[component] = componentWrapper;

                    RegisterComponentOutlets(componentWrapper);
                    RegisterComponentInlets(componentWrapper);
                    RegisterComponentChildReferences(componentWrapper);

                    RaiseComponentRegistered(component);
                }

                componentWrapper.IncrementReferenceCount();
            }
        }

        /// <summary>
        /// Raised when a component is registered with this context.
        /// </summary>
        public Action<object> ComponentRegistered;

        /// <summary>
        /// Raises the ComponentRegistered event.
        /// </summary>
        /// <param name="component"></param>
        private void RaiseComponentRegistered(object component)
        {
            var componentRegistered = ComponentRegistered;
            if (componentRegistered != null) componentRegistered(component);
        }

        /// <summary>
        /// Resumes registering outlets, inlets, and child references, and immediately registers pending 
        /// outlets, inlets, and child references that were queued while hookups were suspended.
        /// </summary>
        public void ResumeHookups()
        {
            lock (_sync)
            {
                _hookupsSuspended = false;
                while (_pendingOutlets.Count > 0) RegisterComponentOutletCore(_pendingOutlets.Dequeue());
                while (_pendingInlets.Count > 0) RegisterComponentInletCore(_pendingInlets.Dequeue());
                while (_pendingChildReferences.Count > 0) RegisterChildReferenceCore(_pendingChildReferences.Dequeue());
            }
        }

        /// <summary>
        /// Suspends registration of outlets, inlets, and child references - all registrations are deferred and queued,
        /// to be performed when hookups are resumed.
        /// </summary>
        public void SuspendHookups()
        {
            lock (_sync) _hookupsSuspended = true;
        }

        /// <summary>
        /// Unregisters the specified component, unregistering its inlets, outlets, and children.
        /// </summary>
        /// <param name="component"></param>
        public void UnregisterComponent(object component)
        {
            if (component == null) throw new ArgumentNullException("component");

            lock (_sync)
            {
                Component componentWrapper;
                if (_registeredComponents.TryGetValue(component, out componentWrapper)
                    && componentWrapper.DecrementReferenceCount() == 0)
                {
                    _registeredComponents.Remove(component);

                    UnregisterComponentOutlets(componentWrapper);
                    UnregisterComponentInlets(componentWrapper);
                    UnregisterComponentChildReferences(componentWrapper);
                }
            }
        }

        /// <summary>
        /// Attempts to register a ChildReference, deferring registration if hookups are presently suspended.
        /// </summary>
        /// <param name="childReference"></param>
        private void RegisterChildReference(ChildReference childReference)
        {
            if (_hookupsSuspended) _pendingChildReferences.Enqueue(childReference);
            else RegisterChildReferenceCore(childReference);
        }
        
        /// <summary>
        /// Registers a ChildReference.
        /// </summary>
        /// <param name="childReference"></param>
        private void RegisterChildReferenceCore(ChildReference childReference)
        {
            //_LogChannels.Trace("Registering child reference " + childReference);

            if (childReference.AutoRegister)
            {
                childReference.ChildComponentAdded += RegisterComponent;
                childReference.ChildComponentRemoved += UnregisterComponent;
                foreach (object childComponent in childReference.ChildComponents) RegisterComponent(childComponent);
            }

            _childReferences.Add(childReference);
        }

        /// <summary>
        /// Registers all child component references exposed by the specified component.
        /// </summary>
        /// <param name="component"></param>
        private void RegisterComponentChildReferences(Component component)
        {
            foreach (ChildReference childReference in component.ChildReferences) RegisterChildReference(childReference);
        }

        /// <summary>
        /// Attempts to register the specified inlet, deferring registration if hookups are presently suspended.
        /// </summary>
        /// <param name="inlet"></param>
        private void RegisterComponentInlet(Inlet inlet)
        {
            if (_hookupsSuspended) _pendingInlets.Enqueue(inlet);
            else RegisterComponentInletCore(inlet);
        }

        /// <summary>
        /// Registers the specified inlet.
        /// </summary>
        /// <param name="inlet"></param>
        private void RegisterComponentInletCore(Inlet inlet)
        {
            //_LogChannels.Trace("Registering inlet " + inlet);

            _inlets.Add(inlet.Contract, inlet);

            if (_outlets.Contains(inlet.Contract))
            {
                foreach (Outlet outlet in _outlets[inlet.Contract])
                {
                    Pair(inlet, outlet);
                }
            }
        }

        /// <summary>
        /// Registers all inlets on the specified component.
        /// </summary>
        /// <param name="component"></param>
        private void RegisterComponentInlets(Component component)
        {
            foreach (Inlet inlet in component.Inlets) RegisterComponentInlet(inlet);
        }

        /// <summary>
        /// Attempts to register the specified outlet, deferring registration if hookups are presently suspended.
        /// </summary>
        /// <param name="outlet"></param>
        private void RegisterComponentOutlet(Outlet outlet)
        {
            if (_hookupsSuspended) _pendingOutlets.Enqueue(outlet);
            else RegisterComponentOutletCore(outlet);
        }
        
        /// <summary>
        /// Registers the specified outlet.
        /// </summary>
        /// <param name="outlet"></param>
        private void RegisterComponentOutletCore(Outlet outlet)
        {
            //_LogChannels.Trace("Registering outlet " + outlet);

            _outlets.Add(outlet.Contract, outlet);

            if (_inlets.Contains(outlet.Contract))
            {
                foreach (Inlet inlet in _inlets[outlet.Contract])
                {
                    Pair(inlet, outlet);
                }
            }
        }

        /// <summary>
        /// Registers all outlets exposed by the specified component.
        /// </summary>
        /// <param name="component"></param>
        private void RegisterComponentOutlets(Component component)
        {
            foreach (Outlet outlet in component.Outlets) RegisterComponentOutlet(outlet);
        }

        /// <summary>
        /// Pairs the specified inlet with the specified outlet.
        /// </summary>
        /// <param name="inlet"></param>
        /// <param name="outlet"></param>
        private void Pair(Inlet inlet, Outlet outlet)
        {
            //_LogChannels.Trace("Pairing " + inlet + " and " + outlet);

            _matchMaker.Pair(inlet, outlet);
        }

        /// <summary>
        /// Unpairs the specified inlet from the specified outlet.
        /// </summary>
        /// <param name="inlet"></param>
        /// <param name="outlet"></param>
        private void Unpair(Inlet inlet, Outlet outlet)
        {
            //_LogChannels.Trace("Unpairing " + inlet + " and " + outlet);

            _matchMaker.Unpair(inlet, outlet);
        }

        /// <summary>
        /// Unregister's a component's child references.
        /// </summary>
        /// <param name="component"></param>
        private void UnregisterComponentChildReferences(Component component)
        {
            foreach (ChildReference childReference in component.ChildReferences) UnregisterChildReference(childReference);
        }

        /// <summary>
        /// Unregisters a component's inlets.
        /// </summary>
        /// <param name="component"></param>
        private void UnregisterComponentInlets(Component component)
        {
            foreach (Inlet inlet in component.Inlets) UnregisterInlet(inlet);
        }

        /// <summary>
        /// Unregisters a component's outlets.
        /// </summary>
        /// <param name="component"></param>
        private void UnregisterComponentOutlets(Component component)
        {
            foreach (Outlet outlet in component.Outlets) UnregisterOutlet(outlet);
        }

        /// <summary>
        /// Unregisters a child reference.
        /// </summary>
        /// <param name="childReference"></param>
        private void UnregisterChildReference(ChildReference childReference)
        {
            //_LogChannels.Trace("Unregistering child reference " + childReference);

            if (childReference.AutoRegister)
            {
                childReference.ChildComponentAdded -= RegisterComponent;
                childReference.ChildComponentRemoved -= UnregisterComponent;
            }

            foreach (object childComponent in childReference.ChildComponents) UnregisterComponent(childComponent);

            _childReferences.Remove(childReference);
        }

        /// <summary>
        /// Unregisters an inlet.
        /// </summary>
        /// <param name="inlet"></param>
        private void UnregisterInlet(Inlet inlet)
        {
            //_LogChannels.Trace("Unregistering inlet " + inlet);

            _inlets.Remove(inlet.Contract, inlet);

            if (_outlets.Contains(inlet.Contract))
            {
                foreach (Outlet outlet in _outlets[inlet.Contract])
                {
                    Unpair(inlet, outlet);
                }
            }
        }

        /// <summary>
        /// Unregisters an outlet.
        /// </summary>
        /// <param name="outlet"></param>
        private void UnregisterOutlet(Outlet outlet)
        {
            //_LogChannels.Trace("Unregistering outlet " + outlet);

            _outlets.Remove(outlet.Contract, outlet);

            if (_inlets.Contains(outlet.Contract))
            {
                foreach (Inlet inlet in _inlets[outlet.Contract])
                {
                    Unpair(inlet, outlet);
                }
            }
        }

        private bool _hookupsSuspended = false;
        private readonly IMatchMaker _matchMaker = null;
        private readonly List<ChildReference> _childReferences = new List<ChildReference>();
        private readonly ModifiableLookup<Contract, Outlet> _outlets = new ModifiableLookup<Contract, Outlet>();
        private readonly ModifiableLookup<Contract, Inlet> _inlets = new ModifiableLookup<Contract, Inlet>();
        private readonly Queue<ChildReference> _pendingChildReferences = new Queue<ChildReference>();
        private readonly Queue<Outlet> _pendingOutlets = new Queue<Outlet>();
        private readonly Queue<Inlet> _pendingInlets = new Queue<Inlet>();
        private readonly Dictionary<object, Component> _registeredComponents = new Dictionary<object, Component>();
        private readonly object _sync = new object();
    }
}
